﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Reflection;
using Microsoft.Crm.Sdk.Metadata;

namespace Streamsol.Crm.XrmDevelopmentToolkit
{
    internal enum CrmAction { Create, Retrieve, Update, Delete, Undefinied }

    public class XrmPropertyCollection : Collection<XrmProperty>
    {
        private static XrmReferenceTypeAttribute GetXrmReferenceAttribute(MemberInfo memberInfo)
        {
            object[] objCustomAtt = memberInfo.GetCustomAttributes(typeof(XrmReferenceTypeAttribute), false);

            if (objCustomAtt == null || objCustomAtt.Length == 0 || objCustomAtt[0] == null)
            {
                if (memberInfo.Name.Equals("Id"))
                    return new XrmReferenceTypeAttribute(memberInfo.ReflectedType.GetCustomAttributes(typeof(XrmEntityAttribute), false).Cast<XrmEntityAttribute>().First().EntityName);

                throw new ArgumentNullException(string.Format("XrmReferenceTypeAttribute not found for member {0}.", memberInfo.Name));
            }
            return (XrmReferenceTypeAttribute)objCustomAtt[0];
        }

        internal static XrmPropertyCollection GetXrmProperties(IXrmEntity obj)
        {
            return GetXrmProperties(obj, CrmAction.Undefinied);
        }

        internal static XrmPropertyCollection GetXrmProperties(IXrmEntity obj, CrmAction crmAction)
        {
            //Get the type info of the object
            Type objType = obj.GetType();

            //Get the XrmEntityAttributes associated with the object
            object[] attributes = objType.GetCustomAttributes(typeof(XrmEntityAttribute), false);

            //Check that we have at least one XrmEntityAttribute declared
            if (attributes.Length == 0)
                throw new ArgumentException(string.Format(@"{0} does not declare an XrmEntityAttribute", objType.Name));

            XrmPropertyCollection xrmProps = new XrmPropertyCollection();

            //Iterate through each property of the object
            foreach (PropertyInfo pi in objType.GetProperties())
            {
                //Even if the Id property doesn't have an attribute it will be mapped...
                //this also prevents mapping to the attributeand type.
                if (pi.Name.Equals("Id"))
                {
                    if (crmAction == CrmAction.Update)
                    {
                        XrmProperty xrmProp = new XrmProperty();
                        xrmProp.Property = XrmExtensions.CreateProperty(AttributeType.PrimaryKey, obj.GetPrimaryIdAttributeName(), new object[] { pi.GetValue(obj, null) });
                        xrmProps.Add(xrmProp);
                    }

                    continue;
                }
                else
                {
                    //Iterate through its custom attributes
                    foreach (XrmFieldMappingAttribute xrmAttribute in pi.GetCustomAttributes(typeof(XrmFieldMappingAttribute), false))
                    {
                        if (!xrmAttribute.IsReadOnly)
                        {
                            if (crmAction == CrmAction.Undefinied
                                || (crmAction == CrmAction.Create && xrmAttribute.IsValidForCreate)
                                || (crmAction == CrmAction.Update && xrmAttribute.IsValidForUpdate))
                            {
                                object[] propValue = new object[2];

                                propValue[0] = pi.GetValue(obj, null);

                                bool isValid = propValue[0] != null;

                                if (isValid)
                                {
                                    string attTypeName = pi.PropertyType.FullName;

                                    if (xrmAttribute.CrmType.IsReferenceType())
                                    {
                                        if ((attTypeName.Equals("System.Guid") && (Guid)propValue[0] == Guid.Empty)
                                            || (attTypeName.Equals("Streamsol.Crm.XrmDevelopmentToolkit.XrmReference") && XrmReference.IsNullOrEmpty(propValue[0] as XrmReference)))
                                        { isValid = false; }
                                        else if (attTypeName.Equals("Streamsol.Crm.XrmDevelopmentToolkit.XrmReference"))
                                        {
                                            XrmReference xrmReference = (XrmReference)propValue[0];

                                            propValue[0] = xrmReference.Id;
                                            propValue[1] = xrmReference.EntityTypeName;
                                        }
                                        else
                                            propValue[1] = GetXrmReferenceAttribute(pi).EntityTypeName;
                                    }
                                    else if (attTypeName.Equals("Streamsol.Crm.XrmDevelopmentToolkit.XrmPicklistInfo"))
                                    {
                                        propValue[0] = ((XrmPicklistInfo)propValue[0]).Value;
                                    }
                                    else if (attTypeName.Equals("Streamsol.Crm.XrmDevelopmentToolkit.XrmStatusInfo"))
                                    {
                                        propValue[0] = ((XrmStatusInfo)propValue[0]).Value;
                                        isValid = false;
                                    }
                                    else if (attTypeName.Equals("System.DateTime") && (DateTime)propValue[0] == DateTime.MinValue)
                                    {
                                        isValid = false;
                                    }

                                    if (isValid)
                                    {
                                        XrmProperty xrmProp = new XrmProperty();
                                        xrmProp.Property = XrmExtensions.CreateProperty(xrmAttribute, propValue);
                                        xrmProps.Add(xrmProp);
                                    }
                                }
                            }
                        }
                    }
                }
            }

            if (xrmProps.Count == 0)
                throw new ArgumentException(string.Format(@"{0} does not declare any writable properties with XrmAttribute", objType.Name));

            return xrmProps;
        }
    }
}
